package com.atguigu.gulimall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;
import com.atguigu.gulimall.product.dao.CategoryDao;
import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.CategoryBrandRelationService;
import com.atguigu.gulimall.product.service.CategoryService;
import com.atguigu.gulimall.product.vo.Catelog2Vo;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    CategoryBrandRelationService categoryBrandRelationService;

    @Autowired
    StringRedisTemplate redisTemplate;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> listWithTree() {
        //1、查出所有分类
        List<CategoryEntity> entities = baseMapper.selectList(null);
        //2、组装成父子的树形结构

        //2.1)找到所有的1级分类
        List<CategoryEntity> level1Menus = entities.stream().filter(categoryEntity ->
                categoryEntity.getParentCid() == 0
        ).map((menu) -> {
            menu.setChildren(getChildrens(menu, entities));
            return menu;
        }).sorted((menu1, menu2) -> {
            return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
        }).collect(Collectors.toList());
        return level1Menus;
    }

    @Override
    public void removeMenuByIds(List<Long> aslist) {
        //TODO //1、检查当前删除的菜单,是否被别的地方使用
        baseMapper.deleteBatchIds(aslist);
    }

    //[2,25,225]
    @Override
    public Long[] findCatelogPath(Long catelogId) {
        List<Long> paths = new ArrayList<>();
        List<Long> parentPath = findParentPath(paths, catelogId);
        Collections.reverse(parentPath);

        return (Long[]) parentPath.toArray(new Long[parentPath.size()]);
    }

    /**
     * 级联更新所有关联的数据
     *
     * @param category
     */
    @Override
    @Transactional
    @CacheEvict(value = {"category"},allEntries = true) //删除分区为category的缓存
   // @CacheEvict(value = {"category"},key = "'getFirstLevelCategories'",allEntries = true) //删除key为getFirstLevelCategories的缓存
    public void updateCascade(CategoryEntity category) {
        this.updateById(category);
        categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
    }

    @Cacheable(value = {"category"},key = "#root.method.name")
    @Override
    public List<CategoryEntity> getFirstLevelCategories() {
        System.out.println("调用了获取一级分类的方法.....");
        List<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
        return categoryEntities;
    }

    /**
     * 使用分布式锁
     *
     * @return
     */
    private Map<String, List<Catelog2Vo>> getIndexCateroryByDistributedLock() {

        String catelogJson = redisTemplate.opsForValue().get("catelogJson");
        if(!StringUtils.isEmpty(catelogJson)){
            Map<String, List<Catelog2Vo>> result = JSON.parseObject(catelogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
            });
            System.out.println("Redis中有数据,直接返回。。。");
            return result;
        }
        //1、占分布式锁。去redis占坑
        String uuid = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
        Map<String, List<Catelog2Vo>> dataFromDb;
        if (lock) {
            System.out.println("获取分布式锁成功.....查询数据库");
            dataFromDb = getDataFromDb();
            String s = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                    "then\n" +
                    "    return redis.call(\"del\",KEYS[1])\n" +
                    "else\n" +
                    "    return 0\n" +
                    "end";
            Long execute = redisTemplate.execute(new DefaultRedisScript<Long>(s, Long.class), Arrays.asList("lock"), uuid);
            return dataFromDb;

        }else {
            System.out.println("获取分布式锁失败...等待重载");
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
            }
            return getIndexCateroryByDistributedLock(); //自旋
        }
    }

    private Map<String, List<Catelog2Vo>> getDataFromDb() {
        List<CategoryEntity> categotyList = this.baseMapper.selectList(null);

        List<CategoryEntity> categories = getParent_cid(categotyList, 0l);

        Map<String, List<Catelog2Vo>> listMap = categories.stream().collect(Collectors.toMap(l1 -> l1.getCatId().toString(), l1 -> {
            List<CategoryEntity> l2Entities = getParent_cid(categotyList, l1.getCatId());
            List<Catelog2Vo> catelog2Vos = null;
            if (l2Entities != null) {
                catelog2Vos = l2Entities.stream().map(l2 -> {
                    Catelog2Vo catelog2Vo = new Catelog2Vo();
                    catelog2Vo.setCatalog1Id(l1.getCatId().toString());
                    catelog2Vo.setId(l2.getCatId().toString());
                    catelog2Vo.setName(l2.getName());
                    List<CategoryEntity> l3Entities = getParent_cid(categotyList, l2.getCatId());
                    if (l3Entities != null) {
                        List<Catelog2Vo.Catelog3Vo> l3List = l3Entities.stream().map(l3 -> {
                            Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo();
                            catelog3Vo.setCatalog2Id(l2.getCatId().toString());
                            catelog3Vo.setId(l3.getCatId().toString());
                            catelog3Vo.setName(l3.getName());
                            return catelog3Vo;
                        }).collect(Collectors.toList());
                        catelog2Vo.setCatalog3List(l3List);
                    }
                    return catelog2Vo;
                }).collect(Collectors.toList());
            }
            return catelog2Vos;
        }));
        String jsonString = JSON.toJSONString(listMap);
        redisTemplate.opsForValue().set("catelogJson", jsonString, 1, TimeUnit.DAYS);
        return listMap;
    }

    private Map<String, List<Catelog2Vo>> getIndexCateroryJsonByLocalLock() {

        synchronized (this) {
            String catelogJson = redisTemplate.opsForValue().get("catelogJson");

            if (!StringUtils.isEmpty(catelogJson)) {

                Map<String, List<Catelog2Vo>> result = JSON.parseObject(catelogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
                });
                System.out.println("Redis中有数据,直接返回。。。");
                return result;
            }
            System.out.println("缓存中不存在值,查询数据库......");
            return getDataFromDb();
        }

    }

    private List<CategoryEntity> getParent_cid(List<CategoryEntity> categotyList, Long parent_id) {
        return categotyList.stream().filter(item -> item.getParentCid() == parent_id).collect(Collectors.toList());
    }

    //使用缓存来存储数据
    @Override
    public Map<String, List<Catelog2Vo>> getCategoryByRedis() {
        String catelogJson = redisTemplate.opsForValue().get("catelogJson");

        if (StringUtils.isEmpty(catelogJson)) {
            System.out.println("查询缓存中无数据,准备查询数据库。。。。");
//            Map<String, List<Catelog2Vo>> categoryByDb = getIndexCateroryJsonByLocalLock(); //使用本地锁
            Map<String, List<Catelog2Vo>> categoryByDb = getIndexCateroryByDistributedLock(); //使用分布式锁
            return categoryByDb;
        }
        Map<String, List<Catelog2Vo>> result = JSON.parseObject(catelogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
        });
        System.out.println("已加载到缓存，不需进入数据库查询");
        return result;
    }


    private List<Long> findParentPath(List<Long> paths, Long catelogId) {
        //1、收集当前节点id
        paths.add(catelogId);
        CategoryEntity byId = this.getById(catelogId);
        if (byId.getParentCid() != 0) {
            findParentPath(paths, byId.getParentCid());
        }
        return paths;
    }

    //递归查找所有菜单的子菜单
    private List<CategoryEntity> getChildrens(CategoryEntity root, List<CategoryEntity> all) {
        List<CategoryEntity> children = all.stream().filter(categoryEntity -> {
            return categoryEntity.getParentCid() == root.getCatId();
        }).map(categoryEntity -> {
            categoryEntity.setChildren(getChildrens(categoryEntity, all));
            return categoryEntity;
        }).sorted((menu1, menu2) -> {
            return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
        }).collect(Collectors.toList());
        return children;
    }

    /**
     * 查询所有的菜单
     * @return
     */
    @Override
    public List<CategoryEntity> getCategoryListTree() {
        //1、查出所有分类
        List<CategoryEntity> allCategoryList = this.baseMapper.selectList(new QueryWrapper<CategoryEntity>(null));
        //2、组装所有的子类id
        //2.1 在所有菜单中查出所有父级的菜单
        List<CategoryEntity> categoryEntityList = allCategoryList.stream().filter((parentCategory) -> {
            //2.1 查出所有父级的菜单
            return parentCategory.getParentCid() == 0;
        }).map(category -> {
            category.setChildren(getCategoryChildrenNodes(category.getCatId(),allCategoryList));
            return category;
        }).collect(Collectors.toList());
        return categoryEntityList;
    }

    private List<CategoryEntity> getCategoryChildrenNodes(Long catId, List<CategoryEntity> allCategoryList) {
        List<CategoryEntity> collect = allCategoryList.stream().filter(categoryEntity -> {
            return categoryEntity.getParentCid() == catId;
        }).map(item -> {
            item.setChildren(getCategoryChildrenNodes(item.getCatId(),allCategoryList));
            return item;
        }).collect(Collectors.toList());
        return collect;
    }

}